今天的守則又跟第三章開頭介紹的([Day 19] Use objects to manage resources)用object來管理資源有關,延伸下去其他的注意事項,就來看看吧!
今天的守則是:
Provide access to raw resources in resource-managing classes。
第三章一開始有說我們要用resource-amanging class來管理資源,但實際情況下,有時候我們需要直接取得資源,就必須要思考提供的方式。例如,現在我們用一個smart pointer的物件來管理資源,但其他function需要直接用到pointer指向的物件,我們用前兩天都有用到的例子來舉例:
std::shared_ptr<Investment> pInv(createInvestment());
int daysHeld(const Investment *pi);
此時如果想將資源傳入,可能會想這樣寫:
int days = daysHeld(pInv); // error!
這會無法編譯,因為daysHeld
接受的是Investment*
這種raw pointer,而pInv
的型態則是shared_ptr<Investment>
。
這要怎麼辦呢?代表這個RAII class─shared_ptr
需要提供方法來取得其中的raw pointer。有兩種方式: 1. explicit conversion;2. implicit conversion。
在這個case中,shared_ptr
有提供explicitconversion的用法─ get()
,像這樣:
int days = daysHeld(pInv.get());
透過get()
,就能取得raw pointer並傳遞給daysHeld
。
而這些smart pointer也有提供implicit conversion的方式,他們有overload dereferencing operator來取裡面的raw pointer,例如這樣:
class Investment
{
public:
bool isTaxFree() const;
};
Investment* createInvestment();
std::shared_ptr<Investment> pi1(createInvestment());
bool taxable1 = !(pi1-> isTaxFree());
bool taxable2 = !((*pi1).isTaxFree());
就可以直接藉由operator->
跟operator*
來access到裡面的raw pointer。
除了smart pointer的例子,我們再來看看,現在可能有一個針對font這種資源的RAII class,他有一個C的API:
FontHandle getFont(); // from C API
void releaseFont(FontHandle fh);
class Font // RAII class
{
public:
explicit Font(FontHandle fh): f(fh){}
~Font() {releaseFont(f);}
private:
FontHandle f; // raw font resource
}
此時,如果我們有很多C API都是針對FontHandle
這種raw respirce來做事,我們就會一直需要把Font
物件轉成FontHandle
,那我們就可以在Font
class裡面設計一個explicit conversion,例如也是用get()
:
class Font
{
public:
FontHandle get() const {return f;}
}
不過這樣那些C API都是要FontHandle
,我們每次使用那些API就都需要多call一個get
:
void changeFontSize(FontHandle f, int newSize); // C API
Font f(getFont());
int newFontSize;
...
changeFontSize(f.get(), newFontSize); // explicit conversion
而有些人就會覺得這樣很麻煩,反而就不想使用Font
這個class,這樣就本末倒置了!因為我們原本用Font
就是想避免直接用FontHandle
可能會有resource leak。為了降低這個風險,我們也可以讓Font
提供implicit conversion的function:
class Font
{
public:
operator FontHandle() const {return f;}
}
這樣那些C API call起來就方便許多:
Font f(getFont());
int newFontSize;
...
changeFontSize(f, newFontSize); // implicit conversion
相對應的缺點就是可能會誤call默默轉型沒發現,如果誤轉型可能會造成FontHandle
流落在外被單獨使用。
結論來說,要讓這個RAII class使用implicit或explicit轉型,要視當下的task而定,精髓就是要讓這個介面用起來, 正確使用容易,錯誤使用難。例如雖然explicit使用起來安全,但有可能implicit的使用會讓user更願意去使用RAII class。
最後再釐清一點,有些人可能會覺得RAII class可以取得raw resource是一件違反封裝特性的事,不過可以思考一點是RAII class的設計不是為了封裝,而是為了保證資源的釋放,且視切入角度,像shared_ptr
這個class,他其實有好好的封裝了reference-counting的機制,而提供了對raw pointer的存取。總之重點就是─隱藏client不需要看到的,但提供client需要存取的。
貼心重點提醒:
- APIs often require access to raw resources, so each RAII class should offer a way to get at the resource it manages.
- Access may be via explicit conversion or implicit conversion. In general, explicit conversion is safer, but implicit conversion is more convenient for clients.
第一就是說明這些RAII class需要提供能取得資源的interface,第二則是提醒兩種做法─explicit與implicit conversion都各有優缺點,需要視情況去做對應設計。